optional: bool,
default_features: bool,
features: Vec<String>,
+
+ // This dependency should be used only for this platform.
+ // `None` means *all platforms*.
+ only_for_platform: Option<String>,
}
impl Dependency {
features: Vec::new(),
default_features: true,
specified_req: None,
+ only_for_platform: None,
}
}
.source_id(id.get_source_id().clone())
}
+ pub fn only_for_platform(mut self, platform: Option<String>) -> Dependency {
+ self.only_for_platform = platform;
+ self
+ }
+
/// Returns false if the dependency is only used to build the local package.
pub fn is_transitive(&self) -> bool { self.transitive }
pub fn is_optional(&self) -> bool { self.optional }
(self.only_match_name || (self.req.matches(id.get_version()) &&
&self.source_id == id.get_source_id()))
}
+
+ /// Returns true if the dependency should be built for this platform.
+ pub fn is_active_for_platform(&self, platform: &str) -> bool {
+ match self.only_for_platform {
+ None => true,
+ Some(ref p) if p.as_slice() == platform => true,
+ _ => false
+ }
+ }
}
#[deriving(PartialEq,Clone,Encodable)]
ResolveEverything,
ResolveRequired(/* dev_deps = */ bool,
/* features = */ &'a [String],
- /* uses_default_features = */ bool),
+ /* uses_default_features = */ bool,
+ /* target_platform = */ Option<&'a str>),
}
impl Resolve {
parent: &Summary,
method: ResolveMethod)
-> CargoResult<CargoResult<Context>> {
+ // Extracting the platform request.
+ let platform = match method {
+ ResolveRequired(_, _, _, platform) => platform,
+ ResolveEverything => None,
+ };
+
// First, figure out our set of dependencies based on the requsted set of
// features. This also calculates what features we're going to enable for
// our own dependencies.
a.len().cmp(&b.len())
});
- activate_deps(cx, registry, parent, deps.as_slice(), 0)
+ activate_deps(cx, registry, parent, platform, deps.as_slice(), 0)
}
-fn activate_deps<R: Registry>(cx: Context,
- registry: &mut R,
- parent: &Summary,
- deps: &[(&Dependency, Vec<Rc<Summary>>, Vec<String>)],
- cur: uint) -> CargoResult<CargoResult<Context>> {
+fn activate_deps<'a, R: Registry>(cx: Context,
+ registry: &mut R,
+ parent: &Summary,
+ platform: Option<&'a str>,
+ deps: &'a [(&Dependency, Vec<Rc<Summary>>, Vec<String>)],
+ cur: uint) -> CargoResult<CargoResult<Context>> {
if cur == deps.len() { return Ok(Ok(cx)) }
let (dep, ref candidates, ref features) = deps[cur];
let method = ResolveRequired(false, features.as_slice(),
- dep.uses_default_features());
+ dep.uses_default_features(), platform);
let key = (dep.get_name().to_string(), dep.get_source_id().clone());
let prev_active = cx.activations.find(&key)
Err(e) => { last_err = Some(e); continue }
}
};
- match try!(activate_deps(my_cx, registry, parent, deps, cur + 1)) {
+ match try!(activate_deps(my_cx, registry, parent, platform, deps, cur + 1)) {
Ok(cx) => return Ok(Ok(cx)),
Err(e) => { last_err = Some(e); }
}
(&'a Dependency, Vec<String>)>> {
let dev_deps = match method {
ResolveEverything => true,
- ResolveRequired(dev_deps, _, _) => dev_deps,
+ ResolveRequired(dev_deps, _, _, _) => dev_deps,
};
// First, filter by dev-dependencies
let deps = parent.get_dependencies();
- let mut deps = deps.iter().filter(|d| d.is_transitive() || dev_deps);
+ let deps = deps.iter().filter(|d| d.is_transitive() || dev_deps);
+
+ // Second, ignoring dependencies that should not be compiled for this platform
+ let mut deps = deps.filter(|d| {
+ match method {
+ ResolveRequired(_, _, _, Some(ref platform)) => {
+ d.is_active_for_platform(platform.as_slice())
+ },
+ _ => true
+ }
+ });
let (mut feature_deps, used_features) = try!(build_features(parent, method));
let mut ret = HashMap::new();
&mut visited));
}
}
- ResolveRequired(_, requested_features, _) => {
+ ResolveRequired(_, requested_features, _, _) => {
for feat in requested_features.iter() {
try!(add_feature(s, feat.as_slice(), &mut deps, &mut used,
&mut visited));
}
}
match method {
- ResolveEverything | ResolveRequired(_, _, true) => {
+ ResolveEverything | ResolveRequired(_, _, true, _) => {
if s.get_features().find_equiv(&"default").is_some() &&
!visited.contains_equiv(&"default") {
try!(add_feature(s, "default", &mut deps, &mut used,
let (packages, resolve_with_overrides, sources) = {
let mut config = try!(Config::new(*shell, jobs, target.clone()));
+ let rustc_host = config.rustc_host().to_string();
let mut registry = PackageRegistry::new(&mut config);
// First, resolve the package's *listed* dependencies, as well as
let _p = profile::start("resolving w/ overrides...");
try!(registry.add_overrides(override_ids));
+
+ let platform = target.as_ref().map(|e| e.as_slice()).or(Some(rustc_host.as_slice()));
let method = resolver::ResolveRequired(dev_deps, features.as_slice(),
- !no_default_features);
+ !no_default_features,
+ platform);
let resolved_with_overrides =
try!(ops::resolve_with_previous(&mut registry, package, method,
Some(&resolve), None));
use std::str;
use core::{SourceMap, Package, PackageId, PackageSet, Resolve, Target};
-use util::{mod, CargoResult, ChainError, internal, Config, profile, Require};
+use util::{mod, CargoResult, ChainError, internal, Config, profile};
use util::human;
use super::{Kind, KindPlugin, KindTarget, Compilation};
}
pub struct Context<'a, 'b> {
- pub rustc_version: String,
pub config: &'b mut Config<'b>,
pub resolve: &'a Resolve,
pub sources: &'a SourceMap<'b>,
host: Layout,
target: Option<Layout>,
target_triple: String,
- host_triple: String,
host_dylib: Option<(String, String)>,
package_set: &'a PackageSet,
target_dylib: Option<(String, String)>,
let (dylib, _) = try!(Context::filename_parts(None));
dylib
};
- let (rustc_version, rustc_host) = try!(Context::rustc_version());
let target_triple = config.target().map(|s| s.to_string());
- let target_triple = target_triple.unwrap_or(rustc_host.clone());
+ let target_triple = target_triple.unwrap_or(config.rustc_host().to_string());
Ok(Context {
- rustc_version: rustc_version,
target_triple: target_triple,
- host_triple: rustc_host,
env: env,
host: host,
target: target,
})
}
- /// Run `rustc` to figure out what its current version string is.
- ///
- /// The second element of the tuple returned is the target triple that rustc
- /// is a host for.
- fn rustc_version() -> CargoResult<(String, String)> {
- let output = try!(util::process("rustc").arg("-v").arg("verbose")
- .exec_with_output());
- let output = try!(String::from_utf8(output.output).map_err(|_| {
- internal("rustc -v didn't return utf8 output")
- }));
- let triple = {
- let triple = output.as_slice().lines().filter(|l| {
- l.starts_with("host: ")
- }).map(|l| l.slice_from(6)).next();
- let triple = try!(triple.require(|| {
- internal("rustc -v didn't have a line for `host:`")
- }));
- triple.to_string()
- };
- Ok((output, triple))
- }
-
/// Run `rustc` to discover the dylib prefix/suffix for the target
/// specified as well as the exe suffix
fn filename_parts(target: Option<&str>)
/// otherwise it corresponds to the target platform.
fn dylib(&self, kind: Kind) -> CargoResult<(&str, &str)> {
let (triple, pair) = if kind == KindPlugin {
- (&self.host_triple, &self.host_dylib)
+ (self.config.rustc_host(), &self.host_dylib)
} else {
- (&self.target_triple, &self.target_dylib)
+ (self.target_triple.as_slice(), &self.target_dylib)
};
match *pair {
None => return Err(human(format!("dylib outputs are not supported \
/// fingerprint.
fn mk_fingerprint<T: Hash>(cx: &Context, data: &T) -> String {
let hasher = SipHasher::new_with_keys(0,0);
- util::to_hex(hasher.hash(&(&cx.rustc_version, data)))
+ util::to_hex(hasher.hash(&(cx.config.rustc_version(), data)))
}
fn calculate_target_fresh(pkg: &Package, dep_info: &Path) -> CargoResult<bool> {
use std::os;
use core::{SourceMap, Package, PackageId, PackageSet, Target, Resolve};
-use util::{CargoResult, ProcessBuilder, CargoError, human, caused_human};
-use util::{Config, internal, ChainError, Fresh, profile};
+use util::{mod, CargoResult, ProcessBuilder, CargoError, human, caused_human};
+use util::{Require, Config, internal, ChainError, Fresh, profile};
use self::job::{Job, Work};
use self::job_queue as jq;
#[deriving(PartialEq, Eq)]
pub enum Kind { KindPlugin, KindTarget }
+/// Run `rustc` to figure out what its current version string is.
+///
+/// The second element of the tuple returned is the target triple that rustc
+/// is a host for.
+pub fn rustc_version() -> CargoResult<(String, String)> {
+ let output = try!(util::process("rustc").arg("-v").arg("verbose")
+ .exec_with_output());
+ let output = try!(String::from_utf8(output.output).map_err(|_| {
+ internal("rustc -v didn't return utf8 output")
+ }));
+ let triple = {
+ let triple = output.as_slice().lines().filter(|l| {
+ l.starts_with("host: ")
+ }).map(|l| l.slice_from(6)).next();
+ let triple = try!(triple.require(|| {
+ internal("rustc -v didn't have a line for `host:`")
+ }));
+ triple.to_string()
+ };
+ Ok((output, triple))
+}
+
// This is a temporary assert that ensures the consistency of the arguments
// given the current limitations of Cargo. The long term fix is to have each
// Target know the absolute path to the build location.
pub use self::cargo_clean::{clean, CleanOptions};
pub use self::cargo_compile::{compile, compile_pkg, CompileOptions};
pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages};
-pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind};
+pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind, rustc_version};
pub use self::cargo_rustc::{KindTarget, KindPlugin, Context, LayoutProxy};
pub use self::cargo_rustc::{PlatformRequirement, PlatformTarget};
pub use self::cargo_rustc::{PlatformPlugin, PlatformPluginAndTarget};
use serialize::{Encodable,Encoder};
use toml;
use core::MultiShell;
+use ops;
use util::{CargoResult, ChainError, Require, internal, human};
use util::toml as cargo_toml;
target: Option<string::String>,
linker: Option<string::String>,
ar: Option<string::String>,
+ rustc_version: string::String,
+ /// The current host and default target of rustc
+ rustc_host: string::String,
}
impl<'a> Config<'a> {
if jobs == Some(0) {
return Err(human("jobs must be at least 1"))
}
+
+ let (rustc_version, rustc_host) = try!(ops::rustc_version());
+
Ok(Config {
home_path: try!(os::homedir().require(|| {
human("Cargo couldn't find your home directory. \
target: target,
ar: None,
linker: None,
+ rustc_version: rustc_version,
+ rustc_host: rustc_host,
})
}
pub fn ar(&self) -> Option<&str> {
self.ar.as_ref().map(|t| t.as_slice())
}
+
+ /// Return the output of `rustc -v verbose`
+ pub fn rustc_version(&self) -> &str {
+ self.rustc_version.as_slice()
+ }
+
+ /// Return the host platform and default target of rustc
+ pub fn rustc_host(&self) -> &str {
+ self.rustc_host.as_slice()
+ }
}
#[deriving(Eq,PartialEq,Clone,Encodable,Decodable)]
dependencies: Option<HashMap<String, TomlDependency>>,
dev_dependencies: Option<HashMap<String, TomlDependency>>,
features: Option<HashMap<String, Vec<String>>>,
+ target: Option<HashMap<String, TomlPlatform>>,
}
#[deriving(Decodable, Clone, Default)]
};
// Collect the deps
- try!(process_dependencies(&mut cx, false, self.dependencies.as_ref()));
- try!(process_dependencies(&mut cx, true, self.dev_dependencies.as_ref()));
+ try!(process_dependencies(&mut cx, false, None, self.dependencies.as_ref()));
+ try!(process_dependencies(&mut cx, true, None, self.dev_dependencies.as_ref()));
+
+ if let Some(targets) = self.target.as_ref() {
+ for (name, platform) in targets.iter() {
+ try!(process_dependencies(&mut cx, false, Some(name.clone()),
+ platform.dependencies.as_ref()));
+ }
+ }
}
let build = match project.build {
}
}
-fn process_dependencies<'a>(cx: &mut Context<'a>, dev: bool,
+fn process_dependencies<'a>(cx: &mut Context<'a>, dev: bool, platform: Option<String>,
new_deps: Option<&HashMap<String, TomlDependency>>)
-> CargoResult<()> {
let dependencies = match new_deps {
.map(|v| v.as_slice()),
&new_source_id));
let dep = dep.transitive(!dev)
+ .only_for_platform(platform.clone())
.features(details.features.unwrap_or(Vec::new()))
.default_features(details.default_features.unwrap_or(true))
.optional(details.optional.unwrap_or(false));
TomlPath(Path),
}
+/// Corresponds to a `target` entry, but `TomlTarget` is already used.
+#[deriving(Decodable)]
+struct TomlPlatform {
+ dependencies: Option<HashMap<String, TomlDependency>>,
+}
+
impl TomlTarget {
fn new() -> TomlTarget {
TomlTarget {
"));
})
+
+#[cfg(target_os = "linux")]
+test!(cargo_platform_specific_dependency {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [project]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [target.i686-unknown-linux-gnu.dependencies.bar]
+ path = "bar"
+ [target.x86_64-unknown-linux-gnu.dependencies.bar]
+ path = "bar"
+ "#)
+ .file("src/main.rs",
+ main_file(r#""{}", bar::gimme()"#, ["bar"]).as_slice())
+ .file("bar/Cargo.toml", r#"
+ [project]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ "#)
+ .file("bar/src/lib.rs", r#"
+ pub fn gimme() -> String {
+ "test passed".to_string()
+ }
+ "#);
+
+ p.cargo_process("build")
+ .exec_with_output()
+ .assert();
+
+ assert_that(&p.bin("foo"), existing_file());
+
+ assert_that(
+ cargo::util::process(p.bin("foo")),
+ execs().with_stdout("test passed\n"));
+})
+
+#[cfg(not(target_os = "linux"))]
+test!(cargo_platform_specific_dependency {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [project]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [target.i686-unknown-linux-gnu.dependencies.bar]
+ path = "bar"
+ [target.x86_64-unknown-linux-gnu.dependencies.bar]
+ path = "bar"
+ "#)
+ .file("src/main.rs",
+ main_file(r#""{}", bar::gimme()"#, ["bar"]).as_slice())
+ .file("bar/Cargo.toml", r#"
+ [project]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ "#)
+ .file("bar/src/lib.rs", r#"
+ extern crate baz;
+
+ pub fn gimme() -> String {
+ format!("")
+ }
+ "#);
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(101));
+})
+
+test!(cargo_platform_specific_dependency_wrong_platform {
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [project]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [target.non-existing-triplet.dependencies.bar]
+ path = "bar"
+ "#)
+ .file("src/main.rs", r#"
+ fn main() {}
+ "#)
+ .file("bar/Cargo.toml", r#"
+ [project]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ "#)
+ .file("bar/src/lib.rs", r#"
+ invalid rust file, should not be compiled
+ "#);
+
+ p.cargo_process("build")
+ .exec_with_output()
+ .assert();
+
+ assert_that(&p.bin("foo"), existing_file());
+
+ assert_that(
+ cargo::util::process(p.bin("foo")),
+ execs());
+
+ let lockfile = p.root().join("Cargo.lock");
+ let lockfile = File::open(&lockfile).read_to_string().assert();
+ assert!(lockfile.as_slice().contains("bar"))
+})